﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace InterpolatorDotNET
{

    public class INTEROPTIONS
    {
        public enum INTERPLATE_OPTIONS { SHOW_QUESTION_MARK, IGNORE_MISSING }
        public INTEROPTIONS()
        {
            this.LIST_JOIN_WITH = ", ";
            this.MISSING_VALUES = INTERPLATE_OPTIONS.SHOW_QUESTION_MARK;
        }
        public INTERPLATE_OPTIONS MISSING_VALUES { get; set; }
        public string LIST_JOIN_WITH { get; set; }
    }

    public class Interpolator
    {
        protected static INTEROPTIONS opt = new INTEROPTIONS();
        /// <summary>
        /// Finds and replaces values within the {classname.property} nomiclature 
        /// </summary>
        /// <param name="str"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string Interpolate(INTEROPTIONS options, String str, params object[] values)
        {
            Interpolator.opt = options;
            return Interpolate(str, values);
        }
        /// <summary>
        /// Finds and replaces values within the {classname.property} nomiclature 
        /// </summary>
        /// <param name="str"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string Interpolate(String str, params object[] values)
        {
            Dictionary<string, object> dictionaries = new Dictionary<string, object>();
            System.Text.RegularExpressions.Regex eval =
              new System.Text.RegularExpressions.Regex("{.+?}", System.Text.RegularExpressions.RegexOptions.Compiled);

            System.Text.RegularExpressions.MatchCollection mc = eval.Matches(str);

            List<string> lookingfor = new List<string>();
            foreach (System.Text.RegularExpressions.Match m in mc)
            {
               string matchkey = m.Value.Trim(new char[] { '{', '}' }).Replace("::", ".").Replace("=>", ".");
               lookingfor.Add(matchkey);
            }

            lookingfor = lookingfor.Distinct().ToList();
            foreach (object foobar in values)
            {
                Dictionary<string, string> mapping = CreatePropertyMap(lookingfor.ToArray(), foobar);

                foreach(KeyValuePair<string,string> pi in mapping)
                {
                    str = System.Text.RegularExpressions.Regex.Replace(str, "{" + pi.Key.Replace("[",@"\[").Replace("]",@"\]") + "}", pi.Value, 
                        System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
                }
            }
            

            return str;
        }
        /// <summary>
        /// Pass in a list of values from within the Directory and they will be searched for within the objects
        /// </summary>
        /// <param name="tobemapped"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static Dictionary<string, string> InterpolateDictionary(Dictionary<string, string> keys, params object[] values)
        {
            List<string> lookingfor = keys.Keys.ToList();
            foreach (object foobar in values)
            {
                Dictionary<string, string> mapping = CreatePropertyMap(lookingfor.ToArray(), foobar);

                foreach (KeyValuePair<string, string> pi in mapping)
                {
                    if (keys.ContainsKey(pi.Key))
                        keys[pi.Key] = pi.Value;
                    else
                        keys.Add(pi.Key, pi.Value);
                }
               
            }

            return keys;
        }
        

        /*
@"Hi, my name is {TestObjectPerson.MyName} and I have a new {TestObjectCar.Manufacturer}.  It is a shiny {TestObjectCar.Color} color when washed.  
Although it cost me {TestObjectCar.RetailPrice} it was worth every penny ... I think.  My wife {TestObjectPerson.Spouse.MyName} and I really like driving it.  
Us and our kids enjoy roadtrips in out new {TestObjectCar.Manufacturer}."
         */
   

        private static Dictionary<string, string> CreatePropertyMap(string[] notations, object objectT)
        {
            if (objectT is System.Data.DataTable)
                return CreatePropertyMap(notations, ((System.Data.DataTable)(objectT)));

            Dictionary<string, string> props = new Dictionary<string, string>();
           

            foreach (string ss in notations)
            {
                var indicies = getIndicies(ss);
                var tt = __CreatePropertyMap( ss, objectT, 0, indicies);
                foreach (var kv in tt)
                    props.Add(kv.Key, kv.Value);
            }

            return props;
        }
        private static Dictionary<string, string> CreatePropertyMap(string[] notations, System.Data.DataTable dt)
        {
            /*
             * {FOO.[0].[Name]} || {[0].[Name]}
             * FOO = TableName
             * [0] = ROW
             * [Name] = COLUMN
             * 
             * (?<table>^\w.+?(?!\[).)(?<row>\[\d+?\])[.](?<column>\[\w+?\])
             * (?<row>\[\d+?\])[.](?<column>\[\w+?\])
            */
            System.Text.RegularExpressions.Regex rx = new System.Text.RegularExpressions.Regex(@"(?<table>^\w.+?(?!\[).)(?<row>\[\d+?\])[.](?<column>\[\w+?\])",
                System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex rx2 = new System.Text.RegularExpressions.Regex(@"(?<row>\[\d+?\])[.](?<column>\[\w+?\])",
               System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            Dictionary<string, string> props = new Dictionary<string, string>();
            foreach (string ss in notations)
            {
                System.Text.RegularExpressions.Match mm = rx.Match(ss);
                if (mm.Success == false)
                    mm = rx2.Match(ss);

                if (mm.Groups["table"].Success)
                {
                    if (string.Compare(dt.TableName, mm.Groups["table"].Value, true) != 0)
                        continue;
                }

                if (isInt(removeTags(mm.Groups["row"].Value)))
                {
                    object oo = dt.Rows[asInt(removeTags(mm.Groups["row"].Value))][removeTags(mm.Groups["column"].Value)];
                    addToDic(props,ss,oo);
                }
                    
            }


            return props;
        }
        private static Dictionary<string, string> __CreatePropertyMap(string notation, object objectT, int iDepth, Dictionary<int,int> indicies)
        {
            string part = notation.Split(':').First().Split(new char[] { '.' }).Skip((1 + iDepth)).First();
            Dictionary<string, string> pp = new Dictionary<string,string>();
            PropertyInfo pi = findProperty(objectT, part);
            if (pi != null)
            {
                if (pi.PropertyType.IsInterface || (pi.PropertyType.IsClass && pi.PropertyType.Name != "String") && ((isEnumerable(pi) == false)))
                {
                    Dictionary<string, string> tt =
                        __CreatePropertyMap(notation, pi.GetValue(objectT, null), ++iDepth, indicies);
                    foreach (var kk in tt)
                        pp.Add(kk.Key, kk.Value);
                }
                else if (pi.PropertyType.IsInterface || (pi.PropertyType.IsClass && pi.PropertyType.Name != "String") && (isEnumerable(pi)))
                {
                    object oo = pi.GetValue(objectT, null);
                   ++iDepth;        // keep the same depth for each item...

                   int iDepthArrayIndex = indicies.ContainsKey(iDepth) ? indicies[iDepth] : -1;

                   int iCounter = -1;
                    foreach (object o in (oo as System.Collections.IEnumerable))
                    {
                        iCounter++;
                        // only run for the item we have represented!
                        if (iDepthArrayIndex != -1 && iDepthArrayIndex != iCounter)
                            continue;
                        

                        Dictionary<string, string> tt =
                            __CreatePropertyMap(notation, o, iDepth, indicies);
                        foreach (var kk in tt)
                        {
                            addToDic(pp, kk.Key, kk.Value);
                        }
                    }
                }
                else
                {

                    string replacewith = Interpolator.opt.MISSING_VALUES == INTEROPTIONS.INTERPLATE_OPTIONS.IGNORE_MISSING ? "" : "?";
                    object oo = pi.GetValue(objectT, null);
                    addToDic(pp, notation, oo);
                }
            }

            return pp;
           
        }
        private static Dictionary<int, int> getIndicies(string notation)
        {
            System.Text.RegularExpressions.Regex eval =
            new System.Text.RegularExpressions.Regex(@"\[.+?\]", System.Text.RegularExpressions.RegexOptions.Compiled);

            // {TestObjectPerson.Kids[0].MyName} :: [0,0],[1,0]
            // {TestObjectPerson.Kids[1].School.Kids[5].MyName} :: [0,0],[1,1],[2,0],[3,5]
            Dictionary<int, int> foo = new Dictionary<int, int>();
            string[] parts = notation.Split('.');

            for (int i=1; i<parts.Length-1; i++)
            {
                string match = eval.Match(parts[i]).Value.Replace("[","").Replace("]","").Trim();
                if (string.IsNullOrEmpty(match))
                    foo.Add(i, -1);
                else
                {
                    int iIndex = -1;
                    int.TryParse(match, out iIndex);
                    foo.Add(i, iIndex);
                }
            }

            return foo;
        }
        private static PropertyInfo findProperty(object foo, string propname)
        {
            // remove any array index 
            propname = System.Text.RegularExpressions.Regex.Replace(propname, @"\[.+?\]", "");

            if (foo.GetType().GetProperty(propname) != null)
                return foo.GetType().GetProperty(propname);
            return foo.GetType().GetProperties()
                .Where(p => string.Compare(p.Name, propname, true) == 0)
                .FirstOrDefault();
        }
        private static bool isEnumerable(PropertyInfo foo)
        {
            return foo.PropertyType.FullName.StartsWith("System.Collections.Generic.");
        }
        private static bool isInt(string s)
        {
            int i = int.MinValue;
            return int.TryParse(s, out i);
        }
        private static int asInt(string s)
        {
            int i = int.MinValue;
            if (int.TryParse(s, out i))
                return i;
            else
                return int.MinValue;
        }
        private static string removeTags(string s)
        {
            return s.Trim(new char[] {'[',']','{','}' });
        }
        private static void addToDic(Dictionary<string, string> dd, string key, object oo)
        {
            string foo = string.Empty;
            if (oo == null)
                return;

            string format = key.Split(':').LastOrDefault();
            if (key.Split(':').Count() == 2)
            {
                if (oo is DateTime)
                {
                    DateTime ooo = (DateTime)oo;
                    foo = ooo.ToString(format);
                }
                else if (oo is int)
                {
                    int ooo = (int)oo;
                    foo = ooo.ToString(format);
                }
                else if (oo is float)
                {
                    float ooo = (float)oo;
                    foo = ooo.ToString(format);
                }
                else if (oo is decimal)
                {
                    decimal ooo = (decimal)oo;
                    foo = ooo.ToString(format);
                }
                            
            }
            else
                foo =  oo.ToString();
            

            if (dd.ContainsKey(key))
                dd[key] = foo;
            else
                dd.Add(key, foo);
        }
        
    }

}
